/*
 * mpo_fileio.cpp
 *
 * Copyright (C) 2005 Matthew P. Ownby
 *
 * This file is part of MPOLIB, a multi-purpose library
 *
 * MPOLIB is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * MPOLIB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

// MPO's NOTE:
//  I may wish to use MPOLIB in a proprietary product some day.  Therefore,
//   the only way I can accept other people's changes to my code is if they
//   give me full ownership of those changes.

// mpo_fileio.cpp
// by Matt Ownby

// Replacement functions for the traditional fopen/fread/fclose/etc functions
// Main purpose: to support files larger than 4 gigs
// Secondary purpose: to not rely on MSCVRT dll in windows

#include "mpo_fileio.h"

#include <stdlib.h>	// for malloc

// if this doesn't crash, you're good to go!
// if anyone knows how to do this check at compile time, let me know!!!
void mpo_test()
{
	if (sizeof(uint64_t) != 8)	// make sure this is really 64-bit
	{
		int i = 0;
		int b;
		
		b = 5 / i;	// force crash
	}
}

bool mpo_file_exists(const char *filename)
{
	bool result = false;
	mpo_io *io = NULL;

	io = mpo_open(filename, MPO_OPEN_READONLY);
	if (io)
	{
		mpo_close(io);
		result = true;
	}
	return result;
}

mpo_io *mpo_open(const char *filename, int flags)
{
	mpo_io *io = NULL;
	bool success = false;

	// dynamically allocate, will be freed by mpo_close
	io = (mpo_io *) malloc(sizeof(mpo_io));

#ifdef WIN32
   memset(io, 0, sizeof(mpo_io));
	io->handle = INVALID_HANDLE_VALUE;

	if (flags == MPO_OPEN_READONLY)
	{
		io->handle = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
	}
	else if (flags == MPO_OPEN_READWRITE)
	{
		io->handle = CreateFile(filename, GENERIC_READ | GENERIC_WRITE,0, NULL,
			OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	}
	else if (flags == MPO_OPEN_CREATE)
	{
		io->handle = CreateFile(filename, GENERIC_WRITE, 0, NULL,
			CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	}
	else if (flags == MPO_OPEN_APPEND)	// append
	{
		// will this seek to the end of the file automatically?
		io->handle = CreateFile(filename, GENERIC_WRITE, 0, NULL,
			OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if (io->handle != INVALID_HANDLE_VALUE)
		{
			mpo_seek(0, MPO_SEEK_END, io);	// seek to the end of the file for appending
		}
	}
	else	// unknown value, error
	{
	}

	// if opening the file succeeded
	if (io->handle != INVALID_HANDLE_VALUE)
	{
		/*
		// GetFileSizeEx apparently only works for Win2k and WinXP so I reverted to GetFileSize for the older windows
		LARGE_INTEGER size;
		GetFileSizeEx(io->handle, &size);	// get the file size, we always want it..
		io->size = size.QuadPart;
		io->eof = false;
		result = true;
		*/

		DWORD hSize = 0;
		DWORD lSize = 0;
		lSize = GetFileSize(io->handle, &hSize);
		// if getting the file size succeeded
		if (lSize != INVALID_FILE_SIZE)
		{
			io->size = hSize;	// assign higher 32-bits initially
			io->size <<= 32;	// shift up 32-bits
			io->size |= lSize;	// and merge in lower 32-bit bits
			io->eof = false;

			// now try to get the file time
			FILETIME last_modified;
			if (GetFileTime(io->handle, NULL, NULL, &last_modified))
			{
				io->time_last_modified = last_modified.dwHighDateTime; // high part
				io->time_last_modified <<= 32;	// shift up 32
				io->time_last_modified |= last_modified.dwLowDateTime;	// low part
				success = true;
			}
			// else it failed for unknown reasons
		}
		// else getfilesize failed
	}
#else
	const char *mode = "rb";	// assume reading
	if (flags == MPO_OPEN_CREATE)
	{
		mode = "wb";
	}
	else if (flags == MPO_OPEN_READWRITE)
	{
		if (mpo_file_exists(filename)) mode = "rb+";	// read/write existing file
		else mode = "wb+";	// create file, open in read/write mode
	}
	else if (flags == MPO_OPEN_APPEND)
	{
		mode = "ab";
	}
	// else unknown... eror
	
	io->handle = MPO_FOPEN(filename, mode);
	if (io->handle)
	{
		struct stat teh_stats;	// to get last modified
		MPO_FSEEK(io->handle, 0, SEEK_END);	// go to end of file
		io->size = MPO_FTELL(io->handle);	// get position (total file size)
		MPO_FSEEK(io->handle, 0, SEEK_SET);	// go to beginning
		io->eof = false;
		if (fstat(fileno(io->handle), &teh_stats) == 0)
		{
			io->time_last_modified = teh_stats.st_mtime;
			success = true;
		}
		// else something went wrong (probably won't ever happen so we won't add error handling)
	}
#endif

	// if something went wrong
	if (!success)
	{
		free(io);
		io = NULL;
	}

	return io;
}

// returns true on success
// EOF is when bytes_read is 0, but true is returned
bool mpo_read (void *buf, size_t bytes_to_read,
			   MPO_BYTES_READ *bytes_read, mpo_io *io)
{
	bool result = false;
	MPO_BYTES_READ backup_bytes_read = 0;	// in case their 'bytes_read' is NULL

	// if they don't care what bytes_read is, then we'll just squelch it
	if (bytes_read == NULL)
	{
		bytes_read = &backup_bytes_read;
	}

#ifdef WIN32
	LPDWORD ptr = bytes_read;
	if (ReadFile(io->handle, buf, (DWORD) bytes_to_read, ptr, NULL) != 0)
	{
		result = true;
	}

	/*
	else
	{
			char s[160];
			DWORD i = GetLastError();
			FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, i, 0, s, sizeof(s), NULL);
			printline(s);
			tmp = "Error message # is " + g_numstr.ToStr((unsigned int) i);
			printline(tmp.c_str());
	}
	*/

#else
	*bytes_read = fread(buf, 1, bytes_to_read, io->handle);
	
	// if we read some bytes, or if we properly got to EOF
	if ((*bytes_read > 0) || ((*bytes_read == 0) || feof(io->handle) != 0))
	{
		result = true;
	}
#endif

	// if we hit EOF, set a flag for convenience
	if ((*bytes_read) != bytes_to_read)
	{
		io->eof = true;
	}

	return result;
}

// returns true on success
bool mpo_write (const void *buf, size_t bytes_to_write, unsigned int *bytes_written, mpo_io *io)
{
	bool result = false;

	unsigned int backup_bytes_written = 0;	// in case their 'bytes_written' is NULL

	// if they don't care what bytes_written is, then we'll just squelch it
	if (bytes_written == NULL)
	{
		bytes_written = &backup_bytes_written;
	}

#ifdef WIN32
	LPDWORD ptr = (LPDWORD) bytes_written;
	if (WriteFile(io->handle, buf, (DWORD) bytes_to_write, ptr, NULL) != 0)
	{
		result = true;
	}
#else
	*bytes_written = fwrite(buf, 1, bytes_to_write, io->handle);
	if (*bytes_written == bytes_to_write)
	{
		result = true;
	}
#endif
	return result;
}

// fseek replacement
// returns true if seek was successful
bool mpo_seek(int64_t offset, seek_type type, mpo_io *io)
{
	bool result = false;

#ifdef WIN32
	/*
	// This code (SetFilePointerEx) works but only for Win2k and WinXP so I have commented it out
	LARGE_INTEGER l;
	l.QuadPart = offset;
	LARGE_INTEGER pre_result;
	pre_result.QuadPart = -1;

	// if seek is successful
	if (SetFilePointerEx(io->handle, l, &pre_result, type) != 0)
	{
		// result should now contain the current position
	}
	else
	{
		pre_result.QuadPart = -1;
	}

	result = true;	// no decent error checking here
	*/

	LONG loffset = (LONG) offset;	// NOTE : assumes long is 32-bits
	LONG hoffset = (LONG) (offset >> 32);
	DWORD pre_result = 0;

	pre_result = SetFilePointer(io->handle, loffset, &hoffset, type);

	// if we potentially got an error ...
//	if (pre_result == INVALID_SET_FILE_POINTER)
	if (pre_result == -1)	// INVALID_SET_FILE_POINTER is -1 but some old visual studio 6's don't have this defined
	{
		result = false;
		pre_result = GetLastError();	// check to see if we really got an error
		if (pre_result == NO_ERROR)
		{
			result = true;
		}
	}

	// no error
	else
	{
		result = true;
	}

#else
	int pre_result = MPO_FSEEK(io->handle, offset, type);
	if (pre_result == 0) result = true;
#endif

	return result;
}

void mpo_close(mpo_io *io)
{
	if (io != NULL)
	{
#ifdef WIN32
		CloseHandle(io->handle);
#else
		fclose(io->handle);
#endif
		io->handle = 0;
		io->eof = false;
		io->size = 0;
		io->time_last_modified = 0;
		free(io);	// de-allocate
	}
	// else we cannot reference a NULL pointer
}

bool mpo_mkdir(const char *dirname)
{
	bool result = false;
#ifdef WIN32
	if (CreateDirectory(dirname, NULL) != 0) result = true;
#else
	// create directory with minimal permissions
	if (mkdir(dirname, 0700) == 0)
	{
		result = true;
	}
#endif
	return result;
}
